# @Desc : Body weight tracking and visualization tool
# @Author : meihongliang
# @Time : 2025-03-01 12:20
import pandas as pd
import matplotlib.pyplot as plt
import statistics
import datetime
from typing import Dict, List, Union
from pathlib import Path

from lw_config import config_util

# Configure global font settings
plt.rcParams['font.family'] = 'Helvetica'
plt.rcParams['axes.edgecolor'] = '#333333'


def safe_float_conversion(value: Union[str, float]) -> float:
    """Convert input to float safely"""
    try:
        return float(value) if value else 0.0
    except (ValueError, TypeError):
        return 0.0


def read_weight_data(file_path: str) -> Dict[str, pd.DataFrame]:
    """Read Excel data with validation"""
    if not Path(file_path).exists():
        raise FileNotFoundError(f"Data file not found: {file_path}")
    return pd.read_excel(file_path, sheet_name=None, keep_default_na=False)


def process_sheet_data(sheet_df: pd.DataFrame) -> List[dict]:
    """Process individual sheet data"""
    valid_data = []
    for _, row in sheet_df.dropna(how="all").iterrows():
        weights = [
            safe_float_conversion(row['体重1']),
            safe_float_conversion(row['体重2'])
        ]
        weights = [w for w in weights if w > 0]

        if weights and row['日期']:
            avg_weight = round(statistics.mean(weights), 1)
            valid_data.append({
                'date': str(row['日期']).split()[0],
                'weight': avg_weight
            })
    return valid_data


def generate_plot(all_weights: List[float], all_dates: List[str], global_avg: float) -> None:
    """Generate optimized visualization plot with value labels"""
    plt.figure(figsize=(20, 8), dpi=100)

    # Main plot line
    main_line, = plt.plot(all_dates, all_weights, marker='o', markersize=6,
                          linewidth=1.5, color='#1f77b4', alpha=0.8, label='Daily Weight')

    # Global average line
    avg_line = plt.axhline(y=global_avg, color='#d62728', linewidth=1.5,
                           linestyle='--', alpha=0.8)

    # 动态标注系统
    max_weight = max(all_weights)
    min_weight = min(all_weights)

    for i, (date, weight) in enumerate(zip(all_dates, all_weights)):
        # 基础定位逻辑
        vertical_offset = 0.4 * (-1 if i % 2 else 1)  # 奇偶点错位
        va_position = 'bottom' if weight < global_avg else 'top'

        # 极值特殊处理
        if weight == max_weight or weight == min_weight:
            vertical_offset *= 2  # 双倍偏移量
            label_color = '#d62728'  # 红色突出
            font_weight = 'bold'
            # 极值箭头标注
            plt.annotate(f'{"MAX" if weight == max_weight else "MIN"}: {weight}',
                         xy=(date, weight),
                         xytext=(date, weight + (2.5 if weight == max_weight else -2.5)),
                         arrowprops=dict(arrowstyle="->", color=label_color, alpha=0.7),
                         color=label_color,
                         fontsize=10,
                         ha='center')
        else:
            label_color = '#2c2c2c'  # 深灰色
            font_weight = 'normal'

        # 数值标注
        plt.text(
            x=date,
            y=weight + vertical_offset,
            s=f'{weight:.1f}',  # 保留一位小数
            ha='center',
            va=va_position,
            rotation=30 if len(all_dates) > 30 else 0,  # 数据点多时倾斜标签
            fontsize=9,
            color=label_color,
            fontweight=font_weight,
            bbox=dict(
                boxstyle='round,pad=0.2',
                facecolor='white',
                edgecolor='none',
                alpha=0.6
            ),
            zorder=3  # 确保在趋势线上方
        )

    # 平均线标注
    plt.annotate(f'Global Avg: {global_avg}',
                 xy=(1.02, global_avg),
                 xycoords=('axes fraction', 'data'),
                 color='#d62728',
                 fontsize=12,
                 va='center',
                 arrowprops=dict(arrowstyle="->", color='#d62728', alpha=0.7))

    # 图表美化设置
    plt.title(f'Weight Trend Analysis ({datetime.datetime.now().year})',
              pad=20, fontsize=14, color='#333333')
    plt.xlabel('Date', labelpad=10, fontsize=11, color='#333333')
    plt.ylabel('Weight', labelpad=10, fontsize=11, color='#333333')

    # 坐标轴格式
    plt.xticks(rotation=45, ha='right', fontsize=9, color='#666666')
    plt.yticks(fontsize=9, color='#666666')

    # 移除多余边框
    plt.gca().spines[:].set_color('#cccccc')
    plt.grid(False)
    plt.box(False)

    # 保存输出
    output_path = Path('./png/day')
    output_path.mkdir(parents=True, exist_ok=True)
    plt.savefig(output_path / f'weight_report_{datetime.datetime.now().strftime("%Y%m%d_%H%M")}.png',
                bbox_inches='tight',
                dpi=300,
                facecolor='white')
    plt.close()


def main() -> None:
    """Main processing function"""
    try:
        file_path = config_util.get_2025_execl()
        all_sheets = read_weight_data(file_path)

        all_weights = []
        all_dates = []

        for sheet_name, sheet_df in all_sheets.items():
            sheet_data = process_sheet_data(sheet_df)
            all_weights.extend([entry['weight'] for entry in sheet_data])
            all_dates.extend([entry['date'] for entry in sheet_data])

        global_avg = round(statistics.mean(all_weights), 1)
        generate_plot(all_weights, all_dates, global_avg)

        print(f"Processed entries: {len(all_weights)}")
        print(f"Analysis complete. Report saved to: png/day/")

    except Exception as e:
        print(f"Error: {str(e)}")


if __name__ == '__main__':
    print(f'20250105--至今{datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")}')
    main()
